package IC.LIR;


import java.util.*;

import IC.AST.*;
import IC.SymbolTable.*;


//the class holds methods and fields offsets
public class ClassLayout {

        private ICClass icClass;
        private int method_counter = 0;
        private int field_counter = 1; // check
        // LinkedHashMap<, >;
        private Map<String,Method> mapName_Method = new LinkedHashMap<String,Method>();
        private Map<Field,Integer> mapField_Offset = new LinkedHashMap<Field,Integer>();
        private Map<Method,Integer> mapMethod_Offset = new LinkedHashMap<Method,Integer>();




        // constructor
        public ClassLayout(ICClass icClass)
        {
                this.icClass = icClass;

                // update methods
                updateMethods_Offset();
                // update fields
                updateFields_Offset() ;
                // update name (string) to method
                updateName_Methods() ;
        }


        ///////////////////////////////////////////////////////////////
        private void updateMethods_Offset()
        {
                // put methods
                for(Method m: icClass.getMethods()){
                        mapMethod_Offset.put(m, method_counter++);
                }
        }
        private void updateFields_Offset()
        {
                // put fields
                for(Field f: icClass.getFields()){
                        mapField_Offset.put(f, field_counter++);
                }
        }
        private void updateName_Methods()
        {
                // string to method
                for(Method m: mapMethod_Offset.keySet()){
                        mapName_Method.put(m.getName(), m);
                }
        }
        /////////////////////////////////////////////////////////////////////



        ////////////////////////   Getters  /////////////////////////////


        //**************************
        public Integer getMethodOffset(Method method){
                return mapMethod_Offset.get(method);
        }

        public Integer getFieldOffset(Field field){
                return mapField_Offset.get(field);
        }

        public Integer getMethodOffsetByName(String name){
                return getMethodOffset(mapName_Method.get(name));
        }

        //**************************

        public ICClass getICClass(){
                return this.icClass;
        }

        public String getClassName(){
                return this.icClass.getName();
        }

        //**************************

        // return method appropriate to the name
        public Method getMethodByName(String name){
                return mapName_Method.get(name);
        }

        //**************************
        public Map<Field,Integer> getMapField_Offset(){
                return this.mapField_Offset;
        }
        // get map of names and methods
        public Map<String,Method> getMapName_Method(){
                return this.mapName_Method;
        }

        public Map<Method,Integer> getMapMethod_Offset(){
                return this.mapMethod_Offset;
        }
        //**************************



        // return number of bytes for allocation
        public int getAllocSize(){
                return 4*(mapField_Offset.size()+1);
        }


        /////////////////////////////////////////////////////////////////////////

        public void add_to_mapMethod_Offset(Method method, Integer offset){
                mapMethod_Offset.put(method, offset);
        }

        public void add_to_mapField_Offset(Field field, Integer offset){
                mapField_Offset.put(field, offset);
        }


        ////////////////////////////////////////////////

        // constructor with super-class
        public ClassLayout (ICClass icClass, ClassLayout superLayout){

                this.icClass = icClass;

                insertToMapMethod_Offset(icClass, superLayout);
                insertToMapField_Offset(icClass, superLayout);
        }

        @SuppressWarnings("unchecked")
        // Update mapField_Offset of icClass
        private void insertToMapField_Offset(ICClass icClass,ClassLayout superLayout) {
                // get clone super-class layout fields offsets
                mapField_Offset = (LinkedHashMap<Field, Integer>)((LinkedHashMap<Field, Integer>)superLayout.getMapField_Offset()).clone();
                // add new fields to mapField_Offset of icClass
                field_counter = mapField_Offset.size()+ 1;
                for(Field f: icClass.getFields()){
                        mapField_Offset.put(f, field_counter++);
                }      
        }

        @SuppressWarnings("unchecked")
        // Update mapMethod_Offset of icClass
        private void insertToMapMethod_Offset(ICClass icClass, ClassLayout superLayout) {
                // get clone super-class layout methods
                mapMethod_Offset = (LinkedHashMap<Method, Integer>)((LinkedHashMap<Method, Integer>)superLayout.getMapMethod_Offset()).clone();

                // Update mapMethod_Offset  of icClass
                //****************************************************************
                //  override existing methods and  add new methods
                method_counter = mapMethod_Offset.size();  //get method_counter of superLayout to set offsets
                for (Method m: icClass.getMethods())
                {
                        boolean isOverriden = false;
                        //check if Method m of icClass defined in mapMethod_Offset of superLayout
                        for (Method definedMethod: mapMethod_Offset.keySet()){
                                //  method already defined(exists in mapMethod_Offset)==> need to be overrided
                                if (m.getName().equals(definedMethod.getName())){
                                        int offset = mapMethod_Offset.get(definedMethod);
                                        mapMethod_Offset.remove(definedMethod);
                                        //replace method from superLayout with method of icClass with original offset of superLayout
                                        mapMethod_Offset.put(m, offset);
                                        isOverriden = true;
                                        break;  // find the method
                                }
                        }

                        // didn't find method in mapMethod_Offset of superLayout ==> insert the method
                        if (!isOverriden)
                                mapMethod_Offset.put(m, method_counter++);
                }

                // create string to method
                for(Method m: mapMethod_Offset.keySet()){
                        mapName_Method.put(m.getName(), m);
                }
        }      

        ///////////////////////////////////////////////////



        // returns the string representation for the icClass dispatch table
        public String getDispatchTable() throws SemanticError
        {
                String dispatch_string = "_DV_"+icClass.getName()+": [";
                dispatch_string += printMethods();
                dispatch_string += "]\n";
                dispatch_string += printFields();

                return dispatch_string;
        }



	// insert fields to dispatch_string
	private String printFields() {
		// get all fields and offsets
		String fieldsOffsets = "# fields offsets:\n";
		for(int i = 0; i < field_counter; i++){
			for (Field f: mapField_Offset.keySet()){
				if (mapField_Offset.get(f) == i)
				{
					//System.out.println(i);
					fieldsOffsets += "# "+f.getName()+": " + i + "\n";
					break;
				}
			}
		}
		return fieldsOffsets;
	}



        // insert methods to dispatch_string
        private String printMethods() throws SemanticError
        {
                String dispatch_string="";

                for(int i = 0; i < method_counter; i++){
                        for (Method m: mapMethod_Offset.keySet()){
                                // method isn't static
                                if ( (!m.isStatic()) && (mapMethod_Offset.get(m) == i))  {         
                                        dispatch_string +="_" + ((ClassSymbolTable) m.getEnclosingScope()).getId() + "_"+m.getName()+",";;//getSymbolOfCurrTableInGlobal().getId();	
                                        break;
                                }
                                else {
                                	//check if method is static
                                    if (m.isStatic()){
                                            //don't insert static method ==> continue
                                            continue;
                                    }
                                }
                        }
                }

                //drop "," from dispatch_string
                if (dispatch_string.endsWith(",")) 
                	dispatch_string = dispatch_string.substring(0, dispatch_string.length()-1);
                return dispatch_string;
        }
}

